# Copyright 2018 Maco
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
'''
Nodes manager.
'''
import json
from urllib.parse import urlparse
from tornado import gen
from tornado.httpclient import AsyncHTTPClient

from blockchain.core.blockchain import Bchain
from blockchain.common.util import printf, request
from blockchain.common import constants


__all__ = ['Nodes']


class NodeServer(object):

    def __init__(self):
        self.nodes = set()

    def __new__(cls, *args):
        '''Create single object.'''
        if not hasattr(cls, '_instance'):
            cls._instance = object.__new__(cls)
        return cls._instance

    @classmethod
    def instance(cls):
        '''Create single object.'''
        if not hasattr(cls, '_instance'):
            cls._instance = object.__new__(cls)
        return cls._instance

    def register(self, addr):
        '''
        添加一个新的节点到nodes
        :param addr: <str> 新节点路径, e.g: http://127.0.0.1:8000
        '''
        url = urlparse(addr)
        if url.netloc not in self.nodes:
            printf('Register node: %s', url.netloc)
            self.nodes.add(url.netloc)

    @gen.coroutine
    def resolve_conflicts(self):
        '''
        共识算法解决冲突，使用网络中最长的链
        :return: <bool> True 链被替换，否则为False
        '''
        new_chain = None
        # 获取当前节点的区块链长度，如果其它节点的链长度大于当前节点，则会被覆盖
        max_length = len(Bchain.chain)
        printf(f'Local chain length: {max_length}')
        # 遍历所有节点，验证当前区块链是否最长
        for node in self.nodes:
            printf(f'Query node chain: {node}')
            resp = yield request(f'http://{node}/api/chain/list')
            if resp and resp.code == constants.CODE_OK:
                data = json.loads(resp.body.decode())
                if data['code'] == constants.CODE_FAILED:
                    printf(f'[{node}]Server has an internal error.')
                    continue
                chain = data['data']['chain']
                length = data['data']['length']
                if length > max_length and Bchain.vaild_chain(chain):
                    max_length = length
                    new_chain = chain
        # 如果其它节点的链长度大于当前节点的链长度，覆盖当前节点的区块链，使用网络中最长的链
        if new_chain:
            Bchain.chain = new_chain
            raise gen.Return(True)
        raise gen.Return(False)

Nodes = NodeServer().instance()
